#include "stdafx.h"
#include "CppUnitTest.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

#include <vex/core/as_intrusive.h>
#include <vex/functional/make_raw_invokable_signal.h>
#include <vex/functional/make_any_delegate.h>
#include <vex.tests/mstest/tests.core/tests.core.handle_policy.h>

namespace Microsoft { namespace VisualStudio { namespace CppUnitTestFramework {
    static std::wstring ToString(std::pair<int, double> const & p_pair)
    {
        std::wstringstream t_str;
        t_str << "pair:" << p_pair.first << ":" << p_pair.second;
        return t_str.str();
    }
}}}
namespace tests { namespace tests_functional_mc_bs2_default_impl {		

    namespace vf = vex::functional;
    namespace mpl = boost::mpl;
    namespace bft = boost::function_types;
    namespace bs2 = boost::signals2;

    TEST_CLASS(test_class)
    {
        BEGIN_TEST_CLASS_ATTRIBUTE()
            TEST_CLASS_ATTRIBUTE(L"Module", L"tests.functional.mc.bs2_default_impl")
        END_TEST_CLASS_ATTRIBUTE();
    public:
        template<class TSignature>
        void bs2_impl_implements_all_contracts_impl(
            vf::implementation::default_value<TSignature>&& p_default = vf::implementation::default_value<TSignature>())
        {
            (void)p_default;
            typedef vf::contract::delegate<TSignature> delegate_t;
            typedef vf::contract::signal<TSignature> mc_signal_t;
            typedef vf::contract::invokable_signal<TSignature> signal_t;

            typedef decltype(vf::make_raw_invokable_signal<TSignature>(
                std::forward< vf::implementation::default_value<TSignature> >(p_default))
            ) make_raw_result_t;

            Assert::IsTrue(std::is_convertible<make_raw_result_t, delegate_t*>::value,
                wlog_message() 
                << "expected convertibility:" << std::endl
                << "from: " << typeid(make_raw_result_t).name() << std::endl 
                << "to: " << typeid(delegate_t*).name() | as_string);

            Assert::IsTrue(std::is_convertible<make_raw_result_t, mc_signal_t*>::value,
                wlog_message() 
                << "expected convertibility:" << std::endl
                << "from: " << typeid(make_raw_result_t).name() << std::endl 
                << "to: " << typeid(mc_signal_t*).name() | as_string);

            Assert::IsTrue(std::is_convertible<make_raw_result_t, signal_t*>::value,
                wlog_message() 
                << "expected convertibility:" << std::endl
                << "from: " << typeid(make_raw_result_t).name() << std::endl 
                << "to: " << typeid(signal_t*).name() | as_string);
        }

        TEST_METHOD(default_value_behaves_as_expected)
        {
            {
                typedef vf::implementation::default_value<int const & ()> default_t;
                const int c_default = 0;
                default_t dv = c_default;
                Assert::IsTrue(std::is_same<int const&, default_t::result_type>::value);
                Assert::IsTrue(std::is_same<int const&, decltype(dv())>::value);
                Assert::AreEqual(&c_default, &dv());
            }
            {
                typedef vf::implementation::default_value<int const * ()> default_t;
                const int c_default = 0;
                default_t dv = &c_default;
                Assert::IsTrue(std::is_same<int const*, default_t::result_type>::value);
                Assert::IsTrue(std::is_same<int const*, decltype(dv())>::value);
                Assert::AreEqual(&c_default, dv());
            }
        }

        TEST_METHOD(bs2_impl_void_arity_0_implements_all_contracts)
        {
            this->bs2_impl_implements_all_contracts_impl<void ()>();
        }

        TEST_METHOD(bs2_impl_int_arity_0_implements_all_contracts)
        {
            this->bs2_impl_implements_all_contracts_impl<int ()>(-1);
        }

        TEST_METHOD(bs2_impl_int_arity_2_implement_all_contracts)
        {
            this->bs2_impl_implements_all_contracts_impl<int (std::string, std::pair<int, double>&)>(-1);
        }

        // this does not compile, who's fault is this? I say boost::signals2 cannot deal with signatures like this
        TEST_METHOD(bs2_impl_int_const_ref_arity_0_implement_all_contracts)
        {
        //    const int c_default = -1;
        //    this->bs2_impl_implements_all_contracts_impl<int const & ()>(c_default);
            Assert::IsTrue(false, L"bs2::signal cannot deal with signatures with reference return type?");
        }

        TEST_METHOD(bs2_impl_int_arity_5_implement_all_contracts)
        {
            this->bs2_impl_implements_all_contracts_impl<
                int (int, char, double, std::string, std::pair<int, double>&)
            >(-1);
        }

        TEST_METHOD(bs2_impl_returning_void_should_be_safely_invokable_if_no_delegate_is_connected)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();
            typedef void (signature_t)();
            //try {
            if (auto t_signal = vex::as_intrusive(vf::make_raw_invokable_signal<signature_t>())) {
                t_signal->invoke();
            } else {
                Assert::Fail(wlog_message() << "make_raw_invokable_signal failed!" | as_string);
            }
            //} catch (std::exception& p_ex) {
            //    Assert::Fail(
            //        wlog_message() 
            //        << "unexpected " << typeid(p_ex).name() 
            //        << " thrown, reason: "
            //        << p_ex.what() | as_string);
            //}
        }

        TEST_METHOD(bs2_impl_returning_default_constructible_should_be_safely_invokable_if_no_delegate_is_connected)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();
            typedef int (signature_t)();
            const int c_default = std::numeric_limits<int>::max();
            if (auto t_signal = vex::as_intrusive(vf::make_raw_invokable_signal<signature_t>(c_default))) {
                Assert::AreEqual(c_default, t_signal->invoke());
            } else {
                Assert::Fail(wlog_message() << "make_raw_invokable_signal failed!" | as_string);
            }
        }

        TEST_METHOD(bs2_impl_arity_0_return_void_should_be_able_to_connect_invoke_and_disconnect_automatically)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();
            typedef void (signature_t)();

            int t_capture = 0;
            if (auto t_delegate = vf::make_any_delegate<signature_t>([&]() { t_capture = 42; })) {
                if (auto t_signal = vex::as_intrusive(vf::make_raw_invokable_signal<signature_t>())) {
                    t_signal->connect(t_delegate.get());
                    t_signal->invoke();
                    Assert::AreEqual(42, t_capture);
                } else {
                    Assert::Fail(wlog_message() << "make_raw_invokable_signal failed!" | as_string);
                }
            } else {
                Assert::Fail(wlog_message() << "make_any_delegate failed!" | as_string);
            }
        }

        TEST_METHOD(bs2_impl_arity_0_return_int_should_be_able_to_connect_invoke_and_disconnect_automatically)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();
            typedef int (signature_t)();
            const int c_default = std::numeric_limits<int>::max();
            if (auto t_delegate = vf::make_any_delegate<signature_t>([]() { return 42; })) {
                if (auto t_signal = vex::as_intrusive(vf::make_raw_invokable_signal<signature_t>(c_default))) {
                    t_signal->connect(t_delegate.get());
                    Assert::AreEqual(42, t_signal->invoke());
                } else {
                    Assert::Fail(wlog_message() << "make_raw_invokable_signal failed!" | as_string);
                }
            } else {
                Assert::Fail(wlog_message() << "make_any_delegate failed!" | as_string);
            }
        }

        TEST_METHOD(bs2_impl_arity_1_return_void_should_be_able_to_connect_invoke_and_disconnect_automatically)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();
            typedef void (signature_t)(int);

            int t_capture = 0;
            if (auto t_delegate = vf::make_any_delegate<signature_t>([&](int p_v) { t_capture = 42 + p_v; })) {
                if (auto t_signal = vex::as_intrusive(vf::make_raw_invokable_signal<signature_t>())) {
                    t_signal->connect(t_delegate.get());
                    t_signal->invoke(1);
                    Assert::AreEqual(43, t_capture);
                } else {
                    Assert::Fail(wlog_message() << "make_raw_invokable_signal failed!" | as_string);
                }
            } else {
                Assert::Fail(wlog_message() << "make_any_delegate failed!" | as_string);
            }
        }

        TEST_METHOD(bs2_impl_arity_5_return_void_should_be_able_to_connect_invoke_and_disconnect_automatically)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();
            typedef void (signature_t)(int, double, std::string const&, std::pair<int, double> const&, char);

            if (auto t_delegate = vf::make_any_delegate<signature_t>(
                [](int p1, double p2, std::string const& p3, std::pair<int, double> const& p4, char p5) {
                    Assert::AreEqual(42, p1);
                    Assert::AreEqual(42.1, p2);
                    Assert::AreEqual(std::string("bimbo"), p3);
                    Assert::AreEqual(std::make_pair<int, double>(42, 42.1), p4);
                    Assert::AreEqual('b', p5);
            })) 
            {
                if (auto t_signal = vex::as_intrusive(vf::make_raw_invokable_signal<signature_t>())) {
                    t_signal->connect(t_delegate.get());
                    t_signal->invoke(42, 42.1, "bimbo", std::make_pair<int, double>(42, 42.1), 'b');
                } else {
                    Assert::Fail(wlog_message() << "make_raw_invokable_signal failed!" | as_string);
                }
            }
        }
    };
}}